// vim:sw=4:ai:aw
#ident	"@(#)Main.cc	1.2	AKK/CII	20090222"

// STL includes
#include <iostream>	// cout, cerr, endl
using std::cout;
using std::cerr;
using std::endl;

// Systeem includes
#include <setjmp.h>	// setjmp/longjmp
#include <signal.h>	// signal()
#include <getopt.h>	// getopt(...)
#include <cstdlib>  // rand()
// Voor meer informatie, zie: man 3 getopt

// En onze eigen includes
#include "asserts.h"	// require/ensure/check/notreached
// ===================================================================

// De diverse geheugenbeheer algoritmes:
#include "RandomFit.h"	// de RandomFit allocator
#include "BuddySystem.h" //toevoegd: include voor eigen allocator
// .... voeg hier je eigen varianten toe ....
/* bijvoorbeeld ....
 #include "firstfit.h"	//
 // ...enz...
 #include "buddy.h"	//
 */

// Deze array van pointers is de koppeling tussen
// main en de (methodes van de) diverse algoritmes.


// ===================================================================

// globale, hulp functie
inline // NB deze 'inline' is alleen wat optimalisatie
bool vraagkans(int r) {
	return (((r >> 5) % 30) < 20); //aangepast
	// De "vraag" kans is 10 op 16, maar we kijken naar een paar
	// bitjes "halverwege" de gegeven r en niet naar de "laatste".
	// >> = schuif bitjes naar rechts
	// %  = modulo = rest na deling
}

// ======================================================

#include "Main.h"	// De pseudo applicatie
Main::Main(Allocator * beheerder, int size) :
		beheerder(beheerder), size(size) {
	// nooit iets geloven ...
	require( beheerder != 0);
	require( size > 0);
	numAlloc = 0; //toegevoegd: inits;
	numAlloc2 = 0;
	numFree = 0;
	numFails = 0;
	numFails2 = 0;
	allocSecondary = false;
	gevraagd = 0;
	gekregen = 0;
	tvSeca = 0;
	tvUseca = 0;
	tvSecf = 0;
	tvUsecf = 0;
}

// actie: na afloop alles netjes opruimen.
Main::~Main() {
	// zijn er nog objecten over?
	while (!objecten.empty()) {
		vergeetOudste();
	}
}

// actie: vraag om geheugen (onze versie van 'new')
void Main::vraagGeheugen(int omvang) {
	if(allocSecondary){
		numAlloc2++;
	}else{
		numAlloc++;
	}
	//cout << endl << "============= Allocation start ===============" << endl;
	//cout << "Vraag " << omvang << "\t";

	// Deze controles overslaan als we aan het testen zijn.
	if (!tflag)
		require( (0 < omvang) && (omvang <= size));
	// is 'omvang' wel geldig ?

	//toegevoegd
	struct timeval begin, end, diff;
	struct timezone tz;
	gettimeofday(&begin, &tz);
	//--------------

	const Area * ap = beheerder->alloc(omvang);

	//toegevoegd
	gettimeofday(&end, &tz);

	//print verschil
	calcDiff(&begin, &end, &diff);
	tvSeca += diff.tv_sec;
	tvUseca += diff.tv_usec;

	if (ap == 0) {
		//cout << "geen geheugen gekregen" << endl;
		if(allocSecondary){
			numFails2++;
		}else{
			numFails++;
		}
		return;
	}

	gevraagd += omvang;
	gekregen += ap->getSize();

	//-------

	// << "kreeg gebied " << ap->getBase() << "..." << ap->getEnd() << ":"
	//<< ap->getSize() << endl;
	//cout << "============= END Allocation ===============" << endl;

	// En nou moeten we controlen of er geen overlap
	// bestaat met wat we al eerder hadden gekregen ...
	ALIterator i;
	for (i = objecten.begin(); i != objecten.end(); i++) {
		const Area * xp = *i; // m.a.w.  objecten[ i ]

		if (((xp->getBase() <= ap->getBase()) && (ap->getBase() <= xp->getEnd())) // ap begint  IN xp !
		|| ((xp->getBase() <= ap->getEnd()) && (ap->getEnd() <= xp->getEnd())) // ap eindigt IN xp !
				) {
			// Dit zou eigenlijk een "fatal error" moeten zijn,
			// maar bij de RandomFit zal dit wel vaker gebeuren
			// dus voorlopig alleen maar melden dat het fout is ...
			cout << "Oeps, nieuwe gebied overlapt met " << xp->getBase()
					<< "..." << xp->getEnd() << ":" << xp->getSize() << endl;
			break; // verder zoeken is niet meer zo zinvol ...
			// ... levert alleen maar meer uitvoer op.
		}
	}
	// Het gekregen gebied moeten we natuurlijk wel onthouden
	objecten.push_back(const_cast<Area*>(ap));
	// NB De 'push_back' schijnt niet te houden van 'const'
	// ... bedenk zelf maar of er misschien nog andere dingen zijn om te controleren
}

//toegevoegde methode
void Main::calcDiff(timeval * first, timeval* second, timeval * difference) {
	if (first->tv_usec > second->tv_usec) {
		second->tv_usec += 1000000;
		second->tv_sec--;
	}
	difference->tv_usec = second->tv_usec - first->tv_usec;
	difference->tv_sec = second->tv_sec - first->tv_sec;
}

// actie: geef een gekregen gebied weer terug (onze versie van 'delete')
void Main::vergeetOudste() {
	//cout << "> FREE < " << "start" << endl;
	require( ! objecten.empty());
	//cout << "> FREE < " << "past require" << endl;
	// hebben we eigenlijk wel wat ?
	const Area * ap = objecten.front(); // het oudste gebied opzoeken, ...

	//cout << "Retour " << ap->getBase() << "..." << ap->getEnd() << ":" << ap->getSize() << endl;
	objecten.pop_front(); // ... uit de lijst halen

	//toegevoegd
	struct timeval begin, end, diff;
	struct timezone tz;
	gettimeofday(&begin, &tz);
	//----------

	beheerder->free(ap); // ... en vrij geven

	//toegevoegd
	gettimeofday(&end, &tz);
	numFree++;
	calcDiff(&begin, &end, &diff);
	tvSecf += diff.tv_sec;
	tvUsecf += diff.tv_usec;
	//----------
}

// =========== ALGORITME METEN =============

// zomaar wat onzin acties
// bedenk zelf wat zinvollers


//toegevoegd
void Main::resetMeasurements(){
	numAlloc = 0;
	numAlloc2 = 0;
	numFree = 0;
	numFails = 0;
	numFails2 = 0;
	allocSecondary = false;
	gevraagd = 0;
	gekregen = 0;
	tvSeca = 0;
	tvUseca = 0;
	tvSecf = 0;
	tvUsecf= 0;
}

void Main::printMeasurements(){
	if(numAlloc2 == 0){
		cout << endl << "========== TESTRESULTAAT: GEFAALDE ALLOCATIES ==========" << endl;
		float percent = ((float) numFails / (float) numAlloc) * 100;
		cout << "gefaalde allocaties: " << numFails << " van de " << numAlloc << "("
				<< percent << "%)" << endl;
	}else{
		cout << endl << "========== TESTRESULTAAT: GEFAALDE ALLOCATIES ==========" << endl;
		float percent = ((float) numFails / (float) numAlloc) * 100;
		cout << "gefaalde primaire allocaties: " << numFails << " van de " << numAlloc << "("
				<< percent << "%)" << endl;
		float percent2 = ((float) numFails2 / (float) numAlloc2) * 100;
		cout << "gefaalde secundaire allocaties: " << numFails2 << " van de " << numAlloc2 << "("
				<< percent2 << "%)" << endl;
	}
	cout << endl << "========== SUCCESVOLLE ALLOCATIES: OVERVLOED  ==========" << endl;
	cout << "totaal gevraagde: " << gevraagd << " en gekregen: " << gekregen
			<< endl;
	int teveel = gekregen - gevraagd;
	float opercent = ((float) teveel / (float) gevraagd) * 100;
	cout << "gemiddeld percentage teveel gealloceerd: " << opercent << endl;
	cout << endl << "============= GEMIDDELDE ALLOCATIE TIJD  ===============" << endl
			<< tvSeca/numAlloc << " seconds and " << (float)(((float)(tvSeca%numAlloc)/(float)numAlloc)*1000000)+((float)tvUseca/(float)numAlloc) << " microseconds" << endl;
	cout << endl << "============= GEMIDDELDE DEALLOCATIE TIJD ==============" << endl
			<< tvSecf/numFree << " seconds and " << (float)(((float)(tvSecf%numFree)/(float)numFree)*1000000)+((float)tvUsecf/(float)numFree) << " microseconds" << endl << endl;
}

void Main::testScenario(int numRequests, int minAanvraag, int maxAanvraag){
// eerst even de random generator wat opschudden
#ifndef	unix		// voor devcpp onder windows,
	srand( time( 0 ) );// windows heeft geen getpid'
#else
	srand(time(0) ^ getpid());
#endif
	resetMeasurements();
	for(int x = 0; x < numRequests; x++){
		if(objecten.empty() || rand()%30 <= 20){
			int r = (rand() % (maxAanvraag-minAanvraag))+minAanvraag;
			vraagGeheugen(r);
		}else{
			vergeetOudste();
		}
	}
	while(!objecten.empty()){
		vergeetOudste();
	}
	cout << "========== TESTSCENARIO ==========" << endl;
	cout << "aantal aanvragen: " << numRequests << endl;
	cout << "minimale aanvraag: " << minAanvraag << endl;
	cout << "maximale aanvraag: " << maxAanvraag << endl << endl;
	printMeasurements();
}

void Main::advTestScenario(int numRequests, int min1, int max1, int min2, int max2){
#ifndef	unix		// voor devcpp onder windows,
	srand( time( 0 ) );// windows heeft geen getpid'
#else
	srand(time(0) ^ getpid());
#endif
	resetMeasurements();
	for(int x = 0; x < numRequests; x++){
		if(objecten.empty() || rand()%30 <= 20){
			int r = 0;
			if(rand()%100 <= 20){
				allocSecondary = true;
				r = (rand()%(max2-min2))+min2;
			}else{
				allocSecondary = false;
				r = (rand()%(max1-min1))+min1;
			}
			vraagGeheugen(r);
		}else{
			vergeetOudste();
		}
	}
	while(!objecten.empty()){
		vergeetOudste();
	}
	cout << endl << "========== TESTSCENARIO ==========" << endl;
	cout << "aantal aanvragen: " << numRequests << endl;
	cout << "primair minimale aanvraag: " << min1 << endl;
	cout << "primair maximale aanvraag: " << max1 << endl;
	cout << "secundair minimale aanvraag: " << min2 << endl;
	cout << "secundair maximale aanvraag: " << max2 << endl;
	printMeasurements();
}
//.........

void Main::randomscenario(int aantal) {
	// eerst even de random generator wat opschudden
#ifndef	unix		// voor devcpp onder windows,
	srand( time( 0 ) );// windows heeft geen getpid'
#else
	srand(time(0) ^ getpid());
#endif
	resetMeasurements();
	// doe tig-keer "iets"
	for (int x = 0; x < aantal; x++) {
		int r = rand(); // gooi de dobbelsteen

		if (objecten.empty() // we hebben nog niets of ...
		|| vraagkans(r)) // ... we kiezen voor geheugen vragen
				{
			// vraag om ruimte, maar niet meer dan wat er zou kunnen zijn
			r = rand(); // dobbelsteen
			r %= (size/10 - 1); // maximaal n-1
			vraagGeheugen(r + 1); // minstens 1 unit
		} else // vrijgeven mits ...
		if (!objecten.empty()) // ... we iets hebben
		{
			// geef het oudste gebied weer terug
			//cout << ">>>>>>>>>>> FREEING <<<<<<<<<<<<<<<" << endl;

			vergeetOudste();
		}
		// else dan doen we een keer niets
	}
	//toegevoed: metingen weergeven
	printMeasurements();
	//----------
}

// =========== ALGORITME TESTEN ==========

// Elke test die hieronder uitgevoerd wordt
// zou een 'assert' failure moeten veroorzaken
// die de normale executie volgorde afbreekt.
// Die "exception" wordt op zijn beurt afgevangen door 'onAbort'
// waarna we weer terug komen in deze functie (dankzij setjmp/longjmp)
// en de volgende test kunnen doen.
// Eigenlijk is dit wat er "onder tafel" gebeurt bij "try ... catch".

jmp_buf jmpbuf; // buffer voor "executie bookmark" (setjmp/longjmp)
// NB Dit moet een globale variabele zijn
// omdat 'onAbort' er ook bij moet kunnen.

void Main::testing() {
	int fase = 0; // Houdt bij hoe ver we zijn in de test procedure.

	tflag = true; // Leg 'vraagGeheugen' even het zwijgen op.

	setjmp( jmpbuf);
	// Maak een "executie bookmark" voor "hier"
	// HINT: Denk aan "try/catch" in Java en C++.
	// Na elke 'test' komen we weer hier terug
	// voor de volgende test.

	cerr << "Stap " << (fase++) << ":\n";

	switch (fase) {
	case 1:
		cerr << "Om niets vragen ...\n";
		vraagGeheugen(0); // dit mag niet kunnen ...
		// ... dus hier zouden we niet mogen komen ...
		cerr << "FAILED\n"; // ... maar de controle heeft gefaald
		/*FALLTHRU*/
	case 2:
		cerr << "Te veel vragen ...\n";
		vraagGeheugen(size + 1);
		cerr << "FAILED\n";
		/*FALLTHRU*/
	case 3:
		cerr << "Te weinig vragen ...\n";
		vraagGeheugen(-1);
		cerr << "FAILED\n";
		/*FALLTHRU*/
	case 4:
		cerr << "Om alles vragen ...\n";
		vraagGeheugen(size); // PAS OP: dit mag dus wel!
		cerr << "OKE\n";
		/*FALLTHRU*/
	case 5:
		// voeg zonodig nog andere testcases toe
	default:
		cerr << "Einde code testen\n";
		tflag = false; // voor het geval dat,
		// dit voorkomt een oneindige lus.
		break;
	}

}

// Functie voor software interrupt afhandeling.
void onAbort(int signo) {
	// Een z.g. "forward declaration"
	extern bool tflag; // De echte declaratie staat verderop

	cerr << "Caught signal " << signo << "...";

	// Zijn we aan het testen ?  ...
	if (tflag && (signo == SIGABRT)) {
		// .. dan was dit "te verwachten".
		cerr << "GOOD.\n";
		longjmp(jmpbuf, 1); // ga terug naar de "executie bookmark"
		// HINT: Denk aan "throw" in Java.
	} else
		// Niet? dan is er echt iets fout gegaan
		cerr << "Eh?\n";

#ifndef	unix		// voor devcpp onder windows,
	system("PAUSE"); // anders is je venster meteen weg.
#endif
	exit(1);
}

// ===================================================================
// Het hoofdprogramma.
// Handel opties af, kies beheerder, enz
// en dan gaan we wat spelen.

// Globale hulpvariabelen voor main en doOptions
Allocator * beheerder = 0; // de gekozen Allocator
int size = 1024; // de omvang van het nep geheugen
int aantal = 1000; // hoe vaak doen we het ...
bool tflag; // true als we de code willen "testen"
// anders wordt er "gemeten".

// Analyseert de meegegeven opties.
// Kan/zal bovenstaande globale variabelen veranderen !
void doOptions(int argc, char * argv[]) {
	char options[] = "s:a:tr2"; // de opties die we willen herkennen ( "2" toegevoegd)
	// s: voor: -s xxx = omvang nepgeheugen
	// a: voor: -a xxx = aantal acties
	// t  voor: -t = code testen (i.p.v. performance meten)
	//
	// Voor de beheeralgoritmes bieden we ...
	// r  voor: -r = RandomFit allocator
	// Als je alle algoritmes zou bouwen dan wordt
	// het zoiets als "s:a:trfnbwpm2"
	// f  voor: -f = first-fit
	// n  voor; -n = next-fit
	//  enz
	// 2  voor: -2 = buddy
	// Voor meer informatie: zie man 3 getopt

	int opt = 0; // "huidige" optie karakter
	do {

		opt = getopt(argc, argv, options); // de uitpluizer

		switch (opt) // welke optie hebben we gezien ...
		{
		case 's': // -s xxx = size
			size = atol(optarg); // atol = "ascii to long"
			break;

		case 'a': // -a xxx = aantal
			aantal = atol(optarg); // atol = "ascii to long"
			break;

		case 't': // -t = code testen
			tflag = true;
			break;

		case 'r': // -r = RandomFit allocator gevraagd
			require( beheerder == 0);
			// eens gekozen, blijft gekozen
			beheerder = new RandomFit();
			break;
		//toegevoegd
		case '2': // -r = RandomFit allocator gevraagd
			require( beheerder == 0);
			// eens gekozen, blijft gekozen
			beheerder = new BuddySystem();
			break;
		//----------
			/* voor Buddy bijvoorbeeld
			 case '2':	// -2 = buddy allocator gevraagd
			 require( beheerder == 0 );
			 beheerder = allocator[ B2_ALLOC ];
			 break;
			 */

		case -1: // = einde opties
			break;

		default: // eh? iets onbekends gevonden
			// foutmelding geven ...
			cerr << "Usage: " << argv[0] << ", valid options are " << options
					<< "\n";
			// en ermee kappen
#ifndef	unix				// voor DevCPP onder windows,
			system("PAUSE");// anders is je venster meteen weg.
#endif
			exit(1); // 1 = "something went wrong"
		}
	} while (opt != -1); // tot einde opties
}

// Programma executie begint hier ...
int main(int argc, char * argv[]) {
	// Installeer de ABORT signal handler.
	signal(SIGABRT, onAbort); // Om de "abort" interrupt af te kunnen vangen
	// die misschien gegenereerd wordt door de
	//   require/check/ensure/notreached
	// macro's van asserts.h als dingen niet kloppen.

	// opties afhandelen.
	// neveneffect: kan diverse globale variabelen veranderen.
	doOptions(argc, argv);
	// is er wel een beheerder gekozen ?
	if (beheerder) {
		// even een veiligheidscontrole.
		// hoe groot is het fictieve geheugen ....
		check( size > 0);

		// oke, omvang beschikbare geheugen instellen
		beheerder->setSize(size);

		// oke, gaan we nu even lekker spelen ...
		Main * mp = new Main(beheerder, size);
		if (tflag) { // De -t optie gezien ?
			mp->testing(); // ga dan de code testen
		} else
			mp->testScenario(500, 1, size/20);
			mp->testScenario(100, size/10, size/2);
			mp->advTestScenario(250, 1, size/20, size/10, size/2);
		// .. uiteraard moet hierboven straks wat anders konden. (gedaan);

		// en daarna alles weer opruimen
		delete mp;
		delete beheerder;

	} else {
		// foutje!
		cerr << "Oeps, geen beheerder gekozen ....\n";
	}

#ifndef	unix		// voor devcpp onder windows,
	system("PAUSE"); // anders is je venster meteen weg.
#endif

	return 0; // 0=alles is goed gegaan
}
